package com.qixia.upload.controller;

import com.creek.data.DataMap;
import com.qixia.upload.dao.model.SystemUploadFile;
import com.qixia.upload.data.FileParam;
import com.qixia.upload.service.SystemUploadFileService;
import com.qixia.upload.util.FileUtils;
import com.qixia.upload.util.JwtUtil;
import com.qixia.upload.util.R;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.errors.ErrorResponseException;
import io.swagger.annotations.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author: Sven
 * date: 2022/10/24 16:06
 * desc: 文件上传下载
 **/
@Api(tags = "文件上传下载")
@RestController
@RequestMapping("/file")
@Slf4j
@CrossOrigin
public class FileUploadController {
    @Value("${file.uploadPath}")
    private String fileUploadPath;
    @Autowired
    private MinioClient minioClient;
    @Value("${minio.endpoint}")
    private String minioEndpoint;
    @Value("${minio.bucketName}")
    private String bucketName;
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");

    private final SystemUploadFileService systemUploadFileService;

    public FileUploadController(SystemUploadFileService systemUploadFileService) {
        this.systemUploadFileService = systemUploadFileService;
    }

    @PostMapping("/upload")
    @ApiOperation("文件上传")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fileType", value = "文件类型0:图片1:附件2:视频", dataType = "String", dataTypeClass = String.class, paramType = "Query")
    })
    public R uploadFile(@ApiParam(value = "上传的文件", allowMultiple = true) MultipartFile file, @RequestParam(required = false, defaultValue = "1") String fileType, HttpServletRequest request) {
        if (file == null || file.isEmpty()) {
            return R.error("文件不能为空");
        }
//        String userId = JwtUtil.getUserId(request);
        //上传附件
        String url;
        String expandedName;
        String originalFilename;
        int index;
        try {
            originalFilename = file.getOriginalFilename();
//            log.info("userId={} upload file,name={}", userId, originalFilename);
            if (originalFilename != null) {
                index = originalFilename.lastIndexOf(".");
                expandedName = originalFilename.substring(index);
            } else {
                return R.error("文件名称不能为空");
            }
            //TODO 可在此对文件类型做判断
            //服务器上文件存放的根路径
            StringBuilder path = new StringBuilder(fileUploadPath);
            //按日期存放文件
            path.append(File.separator).append(SDF.format(new Date()));
            StringBuilder viewPath = new StringBuilder();
            viewPath.append(SDF.format(new Date()));
            //创建文件夹
            File dir = new File(path.toString());
            if (!dir.exists()) {
                boolean flag = FileUtils.createDir(path.toString());
                if (!flag) {
                    log.info("创建文件目录失败.path={}", path);
                    return R.error("创建文件目录失败.");
                }
            }
            //生成文件名,由当前时间戳和六位随机数加文件后缀名组成
            String randomFilename = System.currentTimeMillis() + FileUtils.getRandomString(6) + expandedName;
            //将文件写入服务器
            String fileLocalPath = path + File.separator + randomFilename;
            File localFile = new File(fileLocalPath);
            file.transferTo(localFile);
            viewPath.append(File.separator).append(randomFilename);

            SystemUploadFile systemUploadFile = new SystemUploadFile();
            systemUploadFile.setId(UUID.randomUUID().toString());
            systemUploadFile.setCreateTime(new Date());
//            systemUploadFile.setUserId(userId);
            systemUploadFile.setFileName(originalFilename);
            systemUploadFile.setFileFinalName(randomFilename);
            systemUploadFile.setFilePath(fileLocalPath);
            systemUploadFile.setFileType(fileType);
            systemUploadFile.setViewPath(viewPath.toString());
            systemUploadFileService.insertUploadFile(systemUploadFile);

            return R.ok("上传成功").put("fileId", systemUploadFile.getId());
        } catch (Exception e) {
            log.error("fileUpload fail", e);
            return R.error("上传失败");
        }
    }

    @PostMapping("/uploadAiFile")
    @ApiOperation("智能体文件上传")
    public R uploadAiFile(@RequestBody FileParam data,
                          HttpServletRequest request) {
        try {
            return downloadFile(data.getFileUrl(), fileUploadPath);
        } catch (IOException e) {
            log.error("fileUpload fail", e);
//            return R.error("上传失败");
            return R.error("上传失败");
        }
    }

    private R downloadFile(String fileURL, String saveDir) throws IOException {
        // 创建 URL 对象
        URL url = new URL(fileURL);
        // 打开 URL 连接
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();

        // 获取响应代码
        int responseCode = httpConn.getResponseCode();

        // 如果响应代码是 200 (OK)，则开始下载
        if (responseCode == HttpURLConnection.HTTP_OK) {
            // 获取文件的输入流
            InputStream inputStream = httpConn.getInputStream();
            // 获取文件的名称（从 URL 中提取文件名）
            File file = new File(url.getFile());
            String fileType = file.getName().substring(0, file.getName().indexOf("~"));
            fileType = fileType.substring(fileType.indexOf("."));
            // 创建保存文件的输出流
            String saveFilePath = saveDir + File.separator + System.currentTimeMillis() + FileUtils.getRandomString(6) + fileType;

            FileOutputStream outputStream = new FileOutputStream(saveFilePath);

            // 创建缓冲区
            byte[] buffer = new byte[4096];
            int bytesRead;

            // 读取文件并写入到本地文件中
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }

            // 关闭流
            outputStream.close();
            inputStream.close();

            log.info("文件下载完成，保存到: " + saveFilePath);
            httpConn.disconnect();
            return R.ok(saveFilePath);
        } else {
            log.error("服务器响应码: " + responseCode);
            return R.error("服务器响应码: " + responseCode);
        }

    }

    @GetMapping("/download")
    @ApiOperation("文件下载")
    public void download(String fileId, HttpServletResponse response) {
        SystemUploadFile systemUploadFile = systemUploadFileService.getSystemUploadFileById(fileId);
        File file = new File(systemUploadFile.getFilePath());
        if (file.exists()) {
            try (InputStream ins = Files.newInputStream(file.toPath()); BufferedInputStream bins =
                    new BufferedInputStream(ins); OutputStream outs = response.getOutputStream(); BufferedOutputStream bouts = new BufferedOutputStream(outs)) {
                response.setContentType("application/octet-stream");
                response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(systemUploadFile.getFileName(), "UTF-8"
                ));
                int bytesRead;
                byte[] buffer = new byte[1024];
                while ((bytesRead = bins.read(buffer, 0, 1024)) != -1) {
                    bouts.write(buffer, 0, bytesRead);
                }
                bouts.flush();
            } catch (Exception e) {
                log.error("文件下载出错", e);
            }
        }
    }

    @ApiOperation("视频在线播放")
    @GetMapping(value = "/preview")
    public void Preview(@RequestParam String fileId, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取存储文件
        SystemUploadFile systemUploadFile = systemUploadFileService.getSystemUploadFileById(fileId);
        if (systemUploadFile == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found!");
            return;
        }

        //设置页面缓存
//        if (MisFileType.PICTURE.equals(fileInfo.getFile_type()) || MisFileType.TEXT.equals(fileInfo.getFile_type())) {
        response.setHeader("Cache-Control", "max-age=60");//页面缓存时间60秒
//        }

        //断点下载
        ResumeDownload(request, response, systemUploadFile, "inline");
    }


    /**
     * 支持断点重新下载文件
     *
     * @param fileInfo    文件
     * @param disposition 下载方式 inline:内嵌 attachment:附件
     */
    private static void ResumeDownload(HttpServletRequest request, HttpServletResponse response,
                                       SystemUploadFile fileInfo, String disposition) throws IOException {
        //获取存储文件
        String storageFilePath = fileInfo.getFilePath();
        File storageFile = new File(storageFilePath);
        if (!StringUtils.hasLength(storageFilePath) || !storageFile.exists()) {//相对路径未找到文件
//            storageFilePath = fileInfo.getFile_absolute_path();//根据绝对路径寻找文件
//            storageFile = new File(storageFilePath);
//            if (!StringUtils.hasLength(storageFilePath) || !storageFile.exists()) {
//                response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found!");
            return;
//            }
        }

        //推断类型
        String mimeType = Files.probeContentType(storageFile.toPath());
        if (!StringUtils.hasLength(mimeType)) {
            URL url = new URL("file://" + storageFilePath);
            mimeType = url.openConnection().getContentType();
        }
        //下载开始位置
        long startByte = 0;
        //下载结束位置
        long endByte = storageFile.length() - 1;
        //获取下载范围
        String range = request.getHeader("range");
        if (range != null && range.contains("bytes=") && range.contains("-")) {
            range = range.substring(range.lastIndexOf("=") + 1).trim();
            String[] rangeArray = range.split("-");
            if (rangeArray.length == 1) {
                //Example: bytes=1024-
                if (range.endsWith("-")) {
                    startByte = Long.parseLong(rangeArray[0]);
                } else { //Example: bytes=-1024
                    endByte = Long.parseLong(rangeArray[0]);
                }
            }
            //Example: bytes=2048-4096
            else if (rangeArray.length == 2) {
                startByte = Long.parseLong(rangeArray[0]);
                endByte = Long.parseLong(rangeArray[1]);
            }
        }
        long contentLength = endByte - startByte + 1;

        //HTTP 响应头设置
        //断点续传，HTTP 状态码必须为 206，否则不设置，如果非断点续传设置 206 状态码，则浏览器无法下载
        if (range != null) {
            log.info("断点下载range:{},总大小:{},{}({})", range, storageFile.length(), fileInfo.getFilePath(), fileInfo.getId());
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        }

        if (StringUtils.hasLength(mimeType)) {
            response.setContentType(mimeType);
            response.setHeader("Content-Type", mimeType);
        }
        response.setHeader("Content-Length", String.valueOf(contentLength));
        response.setHeader("Accept-Ranges", "bytes");
        //Content-Range: 下载开始位置-下载结束位置/文件大小
        response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + storageFile.length());
        //Content-disposition: inline; filename=xxx.xxx 表示浏览器内嵌显示该文件
        response.setHeader("Content-Disposition", disposition + "; filename=" + URLEncoder.encode(fileInfo.getFileName(), String.valueOf(StandardCharsets.UTF_8)));

        //传输文件流
        BufferedOutputStream outputStream = null;
        RandomAccessFile randomAccessFile = null;
        //已传送数据大小
        long transmittedLength = 0;
        try {
            //以只读模式设置文件指针偏移量
            randomAccessFile = new RandomAccessFile(storageFile, "r");
            randomAccessFile.seek(startByte);

            outputStream = new BufferedOutputStream(response.getOutputStream());
            byte[] buff = new byte[4096];
            int len;
            while (transmittedLength < contentLength && (len = randomAccessFile.read(buff)) != -1) {
                outputStream.write(buff, 0, len);
                transmittedLength += len;
            }

            outputStream.flush();
            response.flushBuffer();
            log.info("下载完毕:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength,
                    storageFile.length(), fileInfo.getFilePath(), fileInfo.getId());
        } catch (IOException e) {
            if (StringUtils.hasLength(range)) {
                response.setHeader("Content-Range", "bytes " + startByte + "-" + (startByte + transmittedLength) + "/" + storageFile.length());
                log.info("断点下载完毕:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength, storageFile.length(), fileInfo.getFilePath(), fileInfo.getId());
            } else {
                log.info("下载停止:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength, storageFile.length(), fileInfo.getFilePath(), fileInfo.getId());
            }
        } finally {
            try {
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (IOException e) {
                log.error("下载异常," + fileInfo.getId(), e);
            }
        }
    }


    @GetMapping(value = "/fileInfo")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fileId", value = "文件id,多个用,分割", dataType = "String", dataTypeClass = String.class, paramType = "Query")
    })
    @ApiOperation("文件信息")
    public R fileInfo(@ApiParam(value = "文件id", name = "fileId", required = true) @RequestParam String fileId) {
        if (StringUtils.isEmpty(fileId)) {
            return R.error("file id is empty");
        }
        String[] ids = fileId.split(";");
        List<String> idList = new ArrayList<>();
        Collections.addAll(idList, ids);
        //1.通过fileId 获取file path
        List<DataMap> fileList = systemUploadFileService.getSystemUploadFileListById(idList);
        if (CollectionUtils.isEmpty(fileList)) {
            return R.error("file not found");
        }
        DataMap result = new DataMap();
        result.put("fileList", fileList);
        return R.ok(result);
    }

    @PostMapping("/ossUpload")
    @ApiOperation("文件上传")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fileType", value = "文件类型0:图片1:附件2:视频3:音频", dataType = "String", dataTypeClass = String.class, paramType = "Query"),
            @ApiImplicitParam(name = "catalog", value = "文件存放目录,默认upload", dataType = "String", dataTypeClass = String.class, paramType = "Query")
    })
    public R ossUpload(@ApiParam(value = "上传的文件", allowMultiple = true) MultipartFile file,
                       @RequestParam(required = false, defaultValue = "1") String fileType,
                       @RequestParam(required = false, defaultValue = "upload") String catalog,
                       HttpServletRequest request) {
        if (file == null || file.isEmpty()) {
            return R.error("文件不能为空");
        }
        if (!StringUtils.hasLength(catalog)) {
            catalog = "upload";
        }
        String suffix;
        String originalFilename;
        int index;
        try {
            originalFilename = file.getOriginalFilename();
            if (originalFilename != null) {
                index = originalFilename.lastIndexOf(".");
                suffix = originalFilename.substring(index);
            } else {
                return R.error("文件名称不能为空");
            }
            String saveCatalog = "/" + catalog + "/" + SDF.format(new Date());
            String finalName = UUID.randomUUID() + suffix;
            String objectName = saveCatalog + "/" + finalName; // 获取文件名
            // 检查bucket是否存在，如果不存在则创建
            boolean found =
                    minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!found) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }

            // 创建目录结构
//            try {
//                minioClient.putObject(
//                        PutObjectArgs.builder()
//                                .bucket(bucketName)
//                                .object(saveCatalog) // 去掉最后一个斜杠
//                                .stream(new ByteArrayInputStream(new byte[0]), 0, -1)
//                                .contentType("application/x-directory")
//                                .build());
//            } catch (ErrorResponseException e) {
//                // 如果目录已经存在，会抛出异常，忽略它即可
//                if (!e.errorResponse().message().contains("not a regular file")) {
//                    throw e;
//                }
//            }

            // 上传文件
            try (InputStream inputStream = file.getInputStream()) {
                minioClient.putObject(
                        PutObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .stream(inputStream, file.getSize(), -1)
                                .contentType(file.getContentType())
                                .build());
            }

            //生成文件名,由当前时间戳和六位随机数加文件后缀名组成
            SystemUploadFile systemUploadFile = new SystemUploadFile();
            systemUploadFile.setId(UUID.randomUUID().toString());
            systemUploadFile.setCreateTime(new Date());
            systemUploadFile.setFileName(originalFilename);
            systemUploadFile.setFileFinalName(finalName);
            systemUploadFile.setFilePath(objectName);
            systemUploadFile.setFileType(fileType);
            systemUploadFileService.insertUploadFile(systemUploadFile);

            return R.ok("上传成功").put("fileId", systemUploadFile.getId());
        } catch (Exception e) {
            log.error("fileUpload fail", e);
            return R.error("上传失败");
        }
    }

    @GetMapping("/ossDownload")
    @ApiOperation("文件下载")
    public void ossDownload(String fileId, HttpServletResponse response) {
        try {
            SystemUploadFile systemUploadFile = systemUploadFileService.getSystemUploadFileById(fileId);
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("utf-8");

            String fileName = URLEncoder.encode(systemUploadFile.getFileName(), "UTF-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName);
            URL url = new URL(minioEndpoint + "/" + bucketName + systemUploadFile.getFilePath());
            try (DataInputStream dis = new DataInputStream(url.openStream());
                 OutputStream fos = response.getOutputStream()) {

                byte[] buffer = new byte[2048];
                int length;

                while ((length = dis.read(buffer)) > 0) {
                    fos.write(buffer, 0, length);
                }
            }
        } catch (Exception e) {
            log.error("oss文件下载发生异常", e);
        }
    }
}
